package org.caw.hbase.explorer.filter;

import java.util.ArrayList;
import java.util.List;

import org.caw.exception.CawException;
import org.caw.exception.CawParseException;
import org.caw.hbase.explorer.filter.model.ComparisonOperator;
import org.caw.hbase.explorer.filter.model.ExpressionNode;
import org.caw.hbase.explorer.filter.model.Node;
import org.caw.hbase.explorer.filter.model.OperatorNode;

public class Parser {

	public static final char[] SKIP_TOKENS = { ' ', '\n', '\t', '\r' };

	private static final char OPEN_PARENTHESES = '(';

	private static final char CLOSE_PARENTHESES = ')';

	private static final String NULL = "NULL";

	private static final String COLON = ":";

	public static final String CFQ = "[^:()]*";

	private static final String VALUE = "[\\S\\s]+";

	private String COMPARISON_OPERATOR_REGEX = "";

	public String SIMPLE_EXPRESSION_VALIDATION_REGEX;

	public Parser() {
		for (int i = 0; i < ComparisonOperator.values().length; i++) {
			if (i + 1 != ComparisonOperator.values().length) {
				COMPARISON_OPERATOR_REGEX = COMPARISON_OPERATOR_REGEX.concat(ComparisonOperator.values()[i].getValue()).concat("|");
			} else {
				COMPARISON_OPERATOR_REGEX = COMPARISON_OPERATOR_REGEX.concat(ComparisonOperator.values()[i].getValue());
			}
		}
		SIMPLE_EXPRESSION_VALIDATION_REGEX = "\\(" + CFQ + COLON + CFQ + "(" + COMPARISON_OPERATOR_REGEX + ")" + VALUE + "\\)";
	}

	public static class Tokens {
		private List<String> tokens;

		private List<String> operators;

		public List<String> getTokens() {
			return tokens;
		}

		public void setTokens(List<String> tokens) {
			this.tokens = tokens;
		}

		public List<String> getOperators() {
			return operators;
		}

		public void setOperators(List<String> operators) {
			this.operators = operators;
		}

		public void addToken(String token) {
			if (tokens == null) {
				tokens = new ArrayList<String>();
			}
			tokens.add(token);
		}

		public void addOperator(String operator) {
			if (operators == null) {
				operators = new ArrayList<String>();
			}
			operators.add(operator);
		}
	}

	// FIXME escape chars
	private String removeSkipTokens(String expression) {
		int i = 0;

		StringBuilder builder = new StringBuilder();

		while (i < expression.length()) {
			char _char = expression.charAt(i);
			boolean isSkipChar = false;
			for (char skipChar : SKIP_TOKENS) {
				if (skipChar == _char) {
					isSkipChar = true;
				}
			}

			if (!isSkipChar) {
				builder.append(_char);
			}
			i++;
		}

		return builder.toString();
	}

	public static void main(String[] args) throws CawException {
		String expression = "(((A:b=C) AND (A:C=d) AND ((sada:ada!=sada) AND (adsad:asdasd!=2))) AND (C:D<=30) AND (D:E=3))";

		// String exp = "(ADA:ASdA!=31321)";
		Parser p = new Parser();

		System.out.println(p.parse(p.removeSkipTokens(expression)));
	}

	private Tokens splitByLogicalOperator(String expression) {
		Tokens tokens = new Tokens();
		int index = 0;
		int tokenStartIndex = 0;
		int parenthesesCounter = 0;
		expression = removeParentheses(expression);
		while (index < expression.length()) {
			char char_ = expression.charAt(index);
			if (OPEN_PARENTHESES == char_) {
				parenthesesCounter++;
			} else if (CLOSE_PARENTHESES == char_) {
				parenthesesCounter--;
			}
			index++;
			// get token
			if (parenthesesCounter == 0) {
				String token = expression.substring(tokenStartIndex, index);

				tokens.addToken(token);
				// get logical operator
				int operatorStartIndex = index;
				while (index < expression.length()) {
					char_ = expression.charAt(index);
					if (char_ == OPEN_PARENTHESES) {
						tokenStartIndex = index;
						tokens.addOperator(expression.substring(operatorStartIndex, index));
						break;
					}
					index++;
				}
			}
		}
		return tokens;
	}

	private String removeParentheses(String token) {
		token = token.substring(1);
		token = token.substring(0, token.length() - 1);
		return token;
	}

	public Node parseFilters(String expression) throws CawParseException {
		expression = removeSkipTokens(expression);
		return parse(expression);
	}

	private Node parse(String expression) throws CawParseException {
		if (expression.matches(SIMPLE_EXPRESSION_VALIDATION_REGEX)) {
			return parseSimpleExpression(expression);
		} else {
			Tokens tokens = splitByLogicalOperator(expression);
			if (tokens.getTokens() == null || tokens.getTokens().isEmpty() || tokens.getOperators() == null || tokens.getOperators().isEmpty()) {
				throw new CawParseException("Invalid complex token " + expression);
			}
			OperatorNode root = new OperatorNode();
			for (String op : tokens.getOperators()) {
				root.addOperator(op);
			}

			for (String child : tokens.getTokens()) {
				if (child.matches(SIMPLE_EXPRESSION_VALIDATION_REGEX)) {
					root.addChild(parseSimpleExpression(child));
				} else {
					root.addChild(parse(child));
				}
			}
			return root;
		}
	}

	private Node parseSimpleExpression(String expression) throws CawParseException {
		if (expression != null && !expression.matches(SIMPLE_EXPRESSION_VALIDATION_REGEX)) {
			throw new CawParseException("Token " + expression + " is not valid. It should be something like (CF:Q=VAL)");
		}

		expression = removeParentheses(expression);
		String[] expressionTokens = expression.split(COLON + "|" + COMPARISON_OPERATOR_REGEX);

		ComparisonOperator operator = null;
		for (ComparisonOperator op : ComparisonOperator.values()) {
			if (expression.contains(op.getValue())) {
				operator = op;
				break;
			}
		}

		if (expressionTokens.length != 3) {
			throw new CawParseException("Invalid token format " + expression);
		} else {
			ExpressionNode node = new ExpressionNode();
			node.setColumnFamily(expressionTokens[0]);
			node.setQualifier(expressionTokens[1]);

			if (isNull(expressionTokens[2])) {
				node.setValue(null);
			} else {
				node.setValue(expressionTokens[2]);
			}
			node.setOper(operator);
			return node;
		}

	}

	private boolean isNull(String value) {
		if (NULL.equals(value)) {
			return true;
		} else {
			return false;
		}
	}
}
